/**
 * 
 */
package com.celephais.common.client.ui;

import java.util.List;

import com.celephais.common.client.TransferObjectAccessor;
import com.celephais.common.client.model.DataFilter;
import com.celephais.common.client.model.TableColumn;
import com.celephais.common.client.model.TransferObject;
import com.celephais.common.client.model.TransferObjectCreator;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;

/**
 * @author Eugene Shen
 * 
 * A table based on Svetlin Nakov's AdvancedTable example.
 */
public class ObjectAccessTable extends Composite {
	
	private static final String DEFAULT_WIDTH = "640px";
	private static final String DEFAULT_HEIGHT = "480px";
	
	private static final String CR = "\r";
	private static final String LF = "\n";
	private static final String BR = "<BR>";
	
//	private static final String SORT_ASC_SYMBOL = " \u25b2";
//	private static final String SORT_DESC_SYMBOL = " \u25bc";
//	private static final String MARK_COLUMN_TITLE = "\u00bb";
	
	private static final String HEADER_STYLE = "objectAccessTableHeader";
	private static final String NORMAL_STYLE = "objectAccessTableNormal";
	private static final String SELECTED_STYLE = "objectAccessTableSelected";
	
	// Service...
	// Unfortunately, really can't parameterize due to Eclipse problems...
	// All of the @SuppressWarnings down below are due to the lack of
	// parameterization here...
	private TransferObjectAccessor accessor;
	
	// Something that can give us a new TransportableObject...
	private TransferObjectCreator creator;
	
	// Column Data...
	private TableColumn[] columns;
	private int shownCols = 0;
	
	// Actual Object Data...
	private List<? extends TransferObject> displayedObjects;
	
	// Center Table / Grid...
	private final ScrollPanel tableScrollPanel = new ScrollPanel();
	private final Grid table = new Grid();
	private int selectedRow;
	
	// Top Buttons...
	private final HorizontalPanel topPanel = new HorizontalPanel();
	private final Button refreshBtn = new Button("Refresh");
	private final Button addBtn = new Button("Add");
	private final Button editBtn = new Button("Edit");
	private final Button deleteBtn = new Button("Delete");
	
	// Object Edit Dialog...  Dialog?  Or just swap in place?
	private final ObjectEditDialog editDialog = new ObjectEditDialog();
	
	// Error and Message Handling
	private final DialogBox msgDialog = new DialogBox();
	private final HTML msgBody = new HTML();
	
	private final DialogBox updatingDialog = new DialogBox();
	
	public ObjectAccessTable() {
		super();
		accessor = null;
		creator = null;
		columns = null;
		displayedObjects = null;
		selectedRow = -1;
		initComponents();
		this.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
	}
	
	public ObjectAccessTable(TransferObjectAccessor<?> accessor, TransferObjectCreator creator) {
		this();
		setAccessor(accessor);
		setCreator(creator);
	}
	
	public void setAccessor(TransferObjectAccessor<?> accessor) {
		this.accessor = accessor;
	}
	
	public void setCreator(TransferObjectCreator creator) {
		this.creator = creator;
	}
	
	/**
	 * Updates the columns definition.  Gets called as part of updateTable
	 * if columns is null.
	 * @param callback The callback to report success / failure.
	 */
	@SuppressWarnings("unchecked")
	public void updateColumns(final AsyncCallback<TableColumn[]> callback) {
		if (accessor != null) {
			accessor.getColumns(new AsyncCallback<TableColumn[]>() {
				@Override
				public void onFailure(Throwable caught) {
					columns = null;
					shownCols = 0;
					callback.onFailure(caught);
				}

				@Override
				public void onSuccess(TableColumn[] result) {
					if (result != null) {
						columns = result;
						shownCols = 0;
						for (TableColumn col : columns) {
							if (col.isShow()) {
								++shownCols;
							}
						}
						callback.onSuccess(result);
					}
					else {
						columns = null;
						shownCols = 0;
						callback.onFailure(new Throwable("getColumns returned null."));
					}
				}
			});
		}
		else {
			callback.onFailure(new Throwable("No ObjectAccessService assigned."));
		}
	}
	
	/**
	 * Updates the table to display all objects in this collection.
	 * @param forceColumnUpdate Whether or not the column definitions should
	 *                          be forcibly updated.  If false, this method
	 *                          will ONLY call updateColumns if columns
	 *                          is null.
	 */
	public void updateTable(boolean forceColumnUpdate) {
		updatingDialog.center();
		if (forceColumnUpdate || columns == null) {
			this.updateColumns(new AsyncCallback<TableColumn[]>() {
				@Override
				public void onFailure(Throwable caught) {
					updatingDialog.hide();
					showMsg("Load Table Error", "Unable to read Table Columns: " + caught.getMessage());
				}

				@Override
				public void onSuccess(TableColumn[] result) {
					doTableUpdate();
				}
			});
		}
		else {
			doTableUpdate();
		}
	}
	
	@SuppressWarnings("unchecked")
	protected void doTableUpdate() {
		if (accessor != null) {
			if (shownCols > 0) {
				DataFilter[] filters = new DataFilter[1];
				// TODO: Fix this call to include filters, sorting, row limits.
				accessor.getObjects(1, 100, filters, "", false, new AsyncCallback<List<? extends TransferObject>>() {
					@Override
					public void onFailure(Throwable caught) {
						updatingDialog.hide();
						showMsg("Update Table Error", "getObjects call failed: " + caught.getMessage());
					}
	
					@Override
					public void onSuccess(List<? extends TransferObject> result) {
						selectedRow = -1;
						editBtn.setEnabled(false);
						deleteBtn.setEnabled(false);
						displayedObjects = result;
						int numRows = displayedObjects.size() + 1;
						table.resize(numRows, shownCols);
						
						int colNum = 0;
						for (TableColumn col : columns) {
							if (col.isShow()) {
								Label colTitle = new Label(col.getTitle(), true);
//									colTitle.setStylePrimaryName(HEADER_STYLE);
								table.setWidget(0, colNum, colTitle);
								table.getCellFormatter().setStyleName(0, colNum, HEADER_STYLE);
								for (int i=1; i<numRows; ++i) {
									TransferObject to = displayedObjects.get(i-1);
									String value = to.getValue(col.getName());
									if (value == null) {
										value = "";
									}
									value = value.replaceAll(CR+LF, BR).replaceAll(CR, BR).replaceAll(LF, BR);
//									System.out.println("OAT.doTableUpdate: value: " + value);
//									Label cell = new Label(value, true);
									Label cell = new HTML(value, true);
//										cell.setStylePrimaryName(NORMAL_STYLE);
									table.setWidget(i, colNum, cell);
									table.getCellFormatter().setStyleName(i, colNum, NORMAL_STYLE);
								}
								++colNum;
							}
						}
						updatingDialog.hide();
						// Too annoying / intrusive.
//						showMsg("Objects Loaded", "Loaded " + (numRows-1) + " rows.");
					}
				});
			}
			else {
				updatingDialog.hide();
				showMsg("Update Table Error", "No visible columns.");
			}
		}
		else {
			updatingDialog.hide();
			showMsg("Update Table Error", "No ObjectAccessService assigned.");
		}
	}
	
	protected void showMsg(String title, String msg) {
		msgBody.setHTML("<P>" + msg + "</P>");
		msgDialog.setTitle(title);
		msgDialog.setText(title);
		msgDialog.center();
	}
	
	protected void cellClicked(Cell clicked) {
		int colCount = table.getColumnCount();
		if (selectedRow > -1) {
			int row = selectedRow + 1; // Skip header
			for (int i=0; i<colCount; ++i) {
//				table.getWidget(row, i).setStylePrimaryName(NORMAL_STYLE);
				table.getCellFormatter().setStyleName(row, i, NORMAL_STYLE);
			}
		}
		
		if (clicked != null) {
			int row = clicked.getRowIndex();
			if (row > 0) {
				// Select the non-header row clicked...
				selectedRow = row - 1;
				editBtn.setEnabled(true);
				deleteBtn.setEnabled(true);
				for (int i=0; i<colCount; ++i) {
//					table.getWidget(row, i).setStylePrimaryName(SELECTED_STYLE);
					table.getCellFormatter().setStyleName(row, i, SELECTED_STYLE);
				}
			}
			else {
				// TODO: Sort by header column clicked...
				selectedRow = -1;
				editBtn.setEnabled(false);
				deleteBtn.setEnabled(false);
			}
		}
		else {
			selectedRow = -1;
			editBtn.setEnabled(false);
			deleteBtn.setEnabled(false);
		}
	}
	
	/**
	 * Builds the UI Components.
	 */
	private void initComponents() {
		final DockPanel widget = new DockPanel();
		initWidget(widget);
		
		// Build the Center
		// Spacer so table cells won't be too tall.
		VerticalPanel tableSpacer = new VerticalPanel();
		SimplePanel spacer = new SimplePanel();
		tableSpacer.add(tableScrollPanel);
		tableSpacer.setCellWidth(tableScrollPanel, "100%");
		tableSpacer.add(spacer);
//		tableSpacer.setCellWidth(spacer, "100%");
		tableSpacer.setCellHeight(spacer, "100%");
		
//		tableScrollPanel.setSize("100%", "100%");
//		widget.add(tableScrollPanel, DockPanel.CENTER);
//		widget.setCellWidth(tableScrollPanel, "100%");
//		widget.setCellHeight(tableScrollPanel, "100%");
		widget.add(tableSpacer, DockPanel.CENTER);
		widget.setCellWidth(tableSpacer, "100%");
		widget.setCellHeight(tableSpacer, "100%");

		table.setCellPadding(1);
		table.setCellSpacing(1);
		table.setBorderWidth(1);
		tableScrollPanel.add(table);
		table.setSize("100%", "100%");
		
		table.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				cellClicked(table.getCellForEvent(event));
			}
		});
		
		// Build the Top
		widget.add(topPanel, DockPanel.NORTH);
		topPanel.setSpacing(1);
		topPanel.add(refreshBtn);
		topPanel.add(addBtn);
		topPanel.add(editBtn);
		topPanel.add(deleteBtn);
		
		refreshBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				updateTable(false);
			}
		});
		
		addBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				editDialog.show(false, columns, accessor,
						ObjectAccessTable.this, creator.newTransferObject());
			}
		});
		
		editBtn.setEnabled(false);
		editBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if (selectedRow > -1) {
					TransferObject obj = displayedObjects.get(selectedRow);
					if (obj != null) {
						editDialog.show(true, columns, accessor,
								ObjectAccessTable.this, obj);
					}
				}
			}
		});
		
		deleteBtn.setEnabled(false);
		deleteBtn.addClickHandler(new ClickHandler() {
			@SuppressWarnings("unchecked")
			@Override
			public void onClick(ClickEvent event) {
				if (selectedRow > -1) {
					TransferObject obj = displayedObjects.get(selectedRow);
					if (obj != null) {
						accessor.delete(obj, new AsyncCallback<Boolean>() {
							@Override
							public void onFailure(Throwable caught) {
								showMsg("Delete Error", "Unable to delete.  Error: " + caught.getMessage());
							}
							@Override
							public void onSuccess(Boolean result) {
								if (result) {
									updateTable(false);
								}
								else {
									System.out.println("OAT - Unable to delete?");
								}
							}
						});
					}
				}
			}
		});
		
		// Build the message boxes
		final VerticalPanel dialogVPanel = new VerticalPanel();
		final Button closeDlgBtn = new Button("Close");
		dialogVPanel.add(msgBody);
		dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
		dialogVPanel.add(closeDlgBtn);
		msgDialog.add(dialogVPanel);
		
		closeDlgBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				msgDialog.hide();
			}
		});
		
		updatingDialog.setText("Updating Table, please hang on...");
	}
}
